import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.*;


public class MSDserver {


	//configuration values
	public MSDInfo info = new MSDInfo();
	public URSInfo ursInfo = new URSInfo();
	public ArrayList<MSDInfo> childInfoList = new ArrayList<MSDInfo>();
	public MSDserver parent;
	public MSDCache cache = new MSDCache();
	public MSDMulticastDB database;
	private final int listenTimeoutInterval = 1000; //milliseconds
	public boolean debugOutput = true;
	//public String domainURI = "jasonreeder.com";
	private final int addSpaceSendInterval = 60000;
	private long addSpaceSendTimerEnd = 0;
	private final int helloSendInterval = 60000;
	private long helloSendTimerEnd = 0;
	private final int unicastUpdateSendInterval = 60000;
	private long unicastUpdateSendTimerEnd = 0;
	
	//class variables
	public boolean alive = true;
	public boolean closeClientThreads = false;
	public BufferedReader stdIn;
	public boolean isRoot = false;
	public boolean forceImmediateAddSpaceSend = false;
	public String[] cmdLineArgs;
	
	public static void main(String[] args)
	{
		if(args.length < 4)
		{
			System.out.println("ERROR: must call with command line args as follows:");
			System.out.println("  MSDserver [root|child] [myPort] [URS URI String] [URS IP] [URS port]");
			System.exit(1);
		}
		
		InetAddress addr = null;
		String ipAddr = null;
		try {
		    addr = InetAddress.getLocalHost();
		    ipAddr = addr.getHostAddress();
		} catch (UnknownHostException e) {
		}
		MSDserver me = new MSDserver();
		//me.serverNameOrIP = ipAddr.toString();
		me.cmdLineArgs = args; //parse args in startServer method
		me.info.connectionDetails = new ConnectionDetails();
		me.info.connectionDetails.hostNameOrIP = ipAddr;
		me.startServer(); 
		
	}
        
	private void startServer()
	{
		print("Starting server.");
		//---start setup MSD server using information from command line
		if(cmdLineArgs[0].equalsIgnoreCase("root"))
			isRoot = true;
		else
			isRoot = false;
		print("I am " + (isRoot?"":"not ") + "the root." );
		
		info.id = cmdLineArgs[1];
		info.idHash = Helpers.getMD5Hash(info.id);
		info.connectionDetails.port = Integer.parseInt(cmdLineArgs[2]);
		ursInfo.URIString = cmdLineArgs[3];
		ursInfo.connectionDetails.hostNameOrIP = cmdLineArgs[4];
		ursInfo.connectionDetails.port = Integer.parseInt(cmdLineArgs[5]);
		
		if(!isRoot)
		{
			parent = new MSDserver();
			parent.info = new MSDInfo();
			parent.info.connectionDetails = new ConnectionDetails();
			parent.info.connectionDetails.hostNameOrIP = cmdLineArgs[6];
			parent.info.connectionDetails.port = Integer.parseInt(cmdLineArgs[7]);
		}
		//---end setup from command line
		
		//setup root specific properties
		if(isRoot)
		{
			info.overallHashRange = new HashRange();
			info.overallHashRange.hashStart = "00000000000000000000000000000000";
			info.overallHashRange.hashEnd = "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF";
			info.hashRange.hashStart = info.overallHashRange.hashStart;
			info.hashRange.hashEnd = info.overallHashRange.hashEnd;
		}
		
		database = new MSDMulticastDB(info.connectionDetails.hostNameOrIP + "_" + info.connectionDetails.port + "_db.csv");
		
		
		stdIn = new BufferedReader(
                new InputStreamReader(System.in)); //get reference to terminal input
    	
		if(info.connectionDetails.port == -1)
		{
			printError("ERROR: server port not specified properly in config file.");
			System.exit(1);
		}
		if(info.connectionDetails.port < 1 || info.connectionDetails.port > 65535) // port must be in range 1-65535, preferably > 49151
        {
        	printError("ERROR: Port number must be in range 1-65535. Supplied: " + info.connectionDetails.port + ".");
        	System.exit(1);
        }
			
		ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(info.connectionDetails.port);
        } catch (IOException e) {
        	printError("0x001 Error encountered.");
            printError("  Description: Server could not listen on port: " + info.connectionDetails.port + ".");
            System.exit(1);
        }
        
        print("Type \"exit\" and hit enter at any time to close the server.");
        print("Listening for new connections on port: " + info.connectionDetails.port + ".");
        
		Queue<Thread> myWorkerThreads = new LinkedList<Thread>();
        while(alive)
        {
	        try { //set timeout interval for non-blocking listen
	        	serverSocket.setSoTimeout(listenTimeoutInterval); 
	        }
	        catch(SocketException e){
	        	printError("Could not set listen timeout interval on socket accept.");
	        }
	        	
	        try{ //try to accept any waiting connections
	        	Socket clientSocket = serverSocket.accept();
	        	print("Received new connection. " + clientSocket.toString());
    			ClientWorker w = new ClientWorker(this, clientSocket);
                Thread t = new Thread(w);
                t.start(); //spawn a new thread to handle the communication
                myWorkerThreads.add(t);
	        	}
	        catch (SocketTimeoutException ste) {	
	        	//this is OK, just means we did not receive a connection yet
	        	//this is where intermediate server worker code should go
	        	
				try {
					if(stdIn.ready() && stdIn.readLine().toLowerCase().startsWith("exit"))
					{
						alive = false;
						closeClientThreads = true;
						print("Server closed from console.");
					}
				} catch (IOException e) {		}
				
				//-----START PERIODIC MESSAGE CODE------//
				//addSpaceSendInterval = 60000;
				//helloSendInterval = 60000;
				//unicastUpdateSendInterval = 60000;
				
				//periodic HELLO
				if(!isRoot && finishedHelloTimer() && hasParent())
				{
					printDebug("Sending periodic HELLO");
					
					//[hello] [0x0B] [5] [domain count] [ID hash] [msd IP] [msd port]
					//[pmcast status]
					
					//TODO: may want to consider not sending initially unless:
					//  i have no children
					//  some setup time interval has passed
					//  i am sure i have correct and set parent info

					String periodicHelloMessage = "hello 0x0B 5 " +
						getDomainCounts() + " " +
						info.idHash + " " +
						info.connectionDetails.hostNameOrIP + " " +
						info.connectionDetails.port + " " +
						"true"; //pmcast status? not used so hardcoding as true

					
					resetHelloTimer(); //reset so it will occur again
					
					CommunicationChannel msdToParent = 
						new CommunicationChannel(parent.info.connectionDetails);
					msdToParent.openSocketOutIn();
					ClientWorker worker = new ClientWorker();
					worker.out = msdToParent.out;
					worker.msdServer = this;
					printDebug("Waiting for REP-HELLO response.");
					String helloReply = msdToParent.sendMessageRcvReply(periodicHelloMessage);
					printDebug("REP-HELLO response received. Send to ClientWorker() thread to process.");
					worker.processMessage(helloReply);
					msdToParent.closeSocketOutIn();
				}
				
				//periodic ADD-SPACE
				if(forceImmediateSend() || (haveChildren() && finishedAddSpaceTimer()))
				{
					printDebug("Sending periodic ADD-SPACE to " + childInfoList.size() + " children.");
					for(MSDInfo addSpaceChild : childInfoList)
					{
						//[add-space] [message direction] [4] [range start] [range end]
						//[significant bits] [ID hash][\n]

						String periodicAddSpaceMessage = "add-space 0x0B 4 " +
							addSpaceChild.hashRange.hashStart + " " +
							addSpaceChild.hashRange.hashEnd + " " +
							addSpaceChild.hashRange.significantBits + " " +
							addSpaceChild.idHash;
					
						
						CommunicationChannel msdToChild = 
							new CommunicationChannel(addSpaceChild.connectionDetails);
						msdToChild.openSocketOutIn();
						ClientWorker worker = new ClientWorker();
						worker.out = msdToChild.out;
						worker.msdServer = this;
						printDebug("   ADD-SPACE to child: " + periodicAddSpaceMessage);
						msdToChild.sendMessage(periodicAddSpaceMessage);
						msdToChild.closeSocketOutIn();
					}
					resetAddSpaceTimer(); //reset so it will occur again
				}
				
				//periodic UNICAST-UPDATE
				if(finishedUnicastUpdateTimer() && haveLocalURSInfo())
				{
					printDebug("Sending periodic UNICAST-UPDATE");
					//[unicast-update] [0x02] [3] [ID hash] [msd IP] [msd port]
					//need to send to my local URS
					
					String periodicUnicastUpdateMessage = "unicast-update 0x02 3 " +
						info.idHash + " " +
						info.connectionDetails.hostNameOrIP + " " +
						info.connectionDetails.port;
					
					CommunicationChannel msdToURS = 
						new CommunicationChannel(ursInfo.connectionDetails);
					msdToURS.openSocketOutIn();
					ClientWorker worker = new ClientWorker();
					worker.out = msdToURS.out;
					worker.msdServer = this;
					worker.processMessage(msdToURS.sendMessageRcvReply(periodicUnicastUpdateMessage));
					msdToURS.closeSocketOutIn();
					
					resetUnicastUpdateTimer();
				}
				
				
				//-----END PERIODIC MESSAGE CODE------//
	        	
	        }
	        catch (IOException e) {
	        	printError("0x004 Error encountered.");
	        	printError("IOException encountered while trying to listen for connections.");
	        	
	        }
        }   
        
        try {
			serverSocket.close();
		} catch (IOException e) {
			printError("Could not close server socket.");
		}
        try {
			stdIn.close();
		} catch (IOException e) {
			printError("Could not close standard input.");
		}
		
		while(myWorkerThreads.size() > 0)
        {
        	Thread t = myWorkerThreads.remove();
        	if(t != null)
        	{
        		while(t.isAlive())
        		{ //sit here and wait 
        		}
        	}
        }
        print("All ClientWorker threads closed.");
		
		System.exit(0);
        
	}
	


	private int getDomainCounts() {
		int domainCounts = 1;
		for(MSDInfo childMSDForCount : childInfoList)
		{
			domainCounts += childMSDForCount.domainCount;
		}
		return domainCounts;
	}

	public void printError(String errorMessage)
	{
		//can put logic in here to print to log file if needed
		System.err.println("ERROR: " + errorMessage);
	}

	public void print(String message)
	{
		//can put logic in here to print to log file if needed
		System.out.println(message);
	}
	
	public void printDebug(String debugMessage)
	{
		//can put logic in here to print to log file if needed
		if(debugOutput)
			System.out.println("DEBUG: " + debugMessage);
	}
	
	private void resetHelloTimer()
	{
		helloSendTimerEnd = System.currentTimeMillis() + helloSendInterval;
	}
	  
	private boolean finishedHelloTimer()
	{
		return (System.currentTimeMillis() > helloSendTimerEnd);
	}
	
	private void resetAddSpaceTimer()
	{
		addSpaceSendTimerEnd = System.currentTimeMillis() + addSpaceSendInterval;
	}
	  
	private boolean finishedAddSpaceTimer()
	{
		return (System.currentTimeMillis() > addSpaceSendTimerEnd);
	}
	
	private void resetUnicastUpdateTimer()
	{
		unicastUpdateSendTimerEnd = System.currentTimeMillis() + unicastUpdateSendInterval;
	}
	  
	private boolean finishedUnicastUpdateTimer()
	{
		return (System.currentTimeMillis() > unicastUpdateSendTimerEnd);
	}
	
	private boolean haveChildren()
	{
		return (childInfoList != null &&
				childInfoList.size() > 0);
	}
	
	private boolean hasParent()
	{
		return (parent != null &&
				parent.info != null &&
				parent.info.connectionDetails != null &&
				parent.info.connectionDetails.hostNameOrIP != null &&
				parent.info.connectionDetails.port > 0);
	}
	
	private boolean haveLocalURSInfo() {
		return (ursInfo != null &&
				ursInfo.connectionDetails != null &&
				ursInfo.connectionDetails.hostNameOrIP != null &&
				ursInfo.connectionDetails.port > 0);
	}

	private boolean forceImmediateSend() {
		if(forceImmediateAddSpaceSend)
		{
			forceImmediateAddSpaceSend = false;
			return true;
		}
		return false;
	}
		
}


class ClientWorker implements Runnable
{
	public MSDserver msdServer;
	private Socket clientSocket; //used to communicate with connected entity
	private int noMessagesCloseTimeout = 300; //this many seconds of inactivity will force a close of the thread
	private BufferedReader in = null; //to listen
	public PrintWriter out = null; //to talk
	private boolean receivedBye = false;
	
	ClientWorker()
	{
		
	}
	//Constructor
	ClientWorker(MSDserver msdServer, Socket clientSocket)
	{
	    this.msdServer = msdServer;
		this.clientSocket = clientSocket;
	}
	  
	public void run()
	{ //this is where the thread starts on creation
		//print("Communication thread started.");
	    
	    try{
			in = new BufferedReader(new 
					InputStreamReader(clientSocket.getInputStream()));
			out = new PrintWriter(clientSocket.getOutputStream(), true);
	    } catch (IOException e) {
	    	printError("Could not get I/O streams");
	    	System.exit(-1);
	    }

	    //boolean toldToKill = false;
	    boolean messageWaiting = false;
	    long timeoutTimeInSeconds = System.currentTimeMillis()/1000 + noMessagesCloseTimeout;
	    while(true)
	    {
	    	if(receivedBye) 
	    	{
	    		printDebug("Received bye so closing thread properly.");
				break; //BYE message received, so we will close
	    	}
	    	
	    	try {
	    		messageWaiting = false;
				messageWaiting = in.ready();
			} catch (IOException e1) {
				printError("Could not check if in was ready.");
			}
	    	
	    	if(messageWaiting)
	    	{
				String messageIn = null;
				try {
					messageIn = in.readLine();
				} catch (IOException e) {
					printError("Could not read message from socket.");
				}
				
				print("Incoming: " + messageIn);
				processMessage(messageIn);
				//reset timeout since we received a message
				timeoutTimeInSeconds = System.currentTimeMillis()/1000 + noMessagesCloseTimeout;
	    	}
	    	
			if(msdServer.closeClientThreads)
			{
				print("Thread closed by parent.");
				//out.println("BYE 0x0B 0");
				break; //creator signaled to close
			}
			
			if(System.currentTimeMillis()/1000 > timeoutTimeInSeconds)
			{
				print("Timeout (OK) occured at ClientWorker waiting for next message.");
				//out.println("BYE");
				break; //it has been too long since we have received a message
			}
			//sleep for a 1/4 second and check again for messages
			try { Thread.sleep(250);} catch (InterruptedException e) {e.printStackTrace();}

	    }
	    
	    //close connections
	    try{
	    	in.close(); //input stream
	    } catch (IOException e) {
	    	printError("Could not close socket input stream.");
	    }
	    
	    out.close(); //output stream
	    
	    try{
	    	clientSocket.close(); //client socket
	    } catch (IOException e) {
	    	printError("Could not close socket.");
	    }
	    
	    printDebug("Thread finally closed.");
	}
	
	public void processMessage(String messageIn)
	{
		//this is where all the logic will go for processing messages
		boolean sendResponse = true;
		String response = null;
		BaseMessage message = new BaseMessage();
		if(!message.parseBaseMessage(messageIn))
		{
			response = "ERROR 0x03 0 Malformed request: " + messageIn;
		}
		else
		{
			message.parseMessageFurther();
			switch(message.type) {
		     case UNKNOWN:
		       response = "ERROR 0x05 0 Unknown request: " + messageIn;
		       break;
		     case BYE:
		       receivedBye = true;
		       //TODO: send proper BYE direction
		       response = "BYE 0x08 0";
		       break;
		     //client -> MSD start
		     case REGISTER:
		       boolean status = true;
		       printDebug(" $ Starting REGISTER logic");
		       int falseStatusCount = 0;
		       String[] keywords = Helpers.extractFields(message.arguments.get("[keyword list]"), ",");
		       printDebug(" $ Registering keywords: " + Arrays.deepToString(keywords));
		       for (String keyword : keywords){
		    	   String keywordHash = Helpers.getMD5Hash(keyword);
		    	   if(msdServer.info.hashRange.isInRange(keywordHash)){
		    		   printDebug(" $ keyword: " + keyword + " is in local hash range");
		    		   status = msdServer.database.createRecord(keywordHash + "," + keyword + "," + 
		    				   									message.arguments.get("[char-set]") + "," +
										    				    message.arguments.get("[expiration time]") + "," +
																message.arguments.get("[start time]") + "," +
																message.arguments.get("[URS Identifier]") + "," +
																message.arguments.get("[channel IP]") + "," +
																message.arguments.get("[channel port]") + "," +
																message.arguments.get("[unicast IP]") + "," +
																message.arguments.get("[unicast port]") + "," +
																message.arguments.get("[channel scope]") + "," +
																message.arguments.get("[Geographic CN]") + "," +
																message.arguments.get("[Lat]") + "," +
																message.arguments.get("[Long]") + "," +
																message.arguments.get("[network type]") + "," +
																message.arguments.get("[source IP]") + "," +
																message.arguments.get("[stream type]") + " " +
																message.arguments.get("[preferred application]") + "," +
																message.arguments.get("[CLI argument]") + "," +
																message.arguments.get("[mime-type]"));
		    		   if(!status){
		    			   falseStatusCount++;
		    			   printDebug(" $ keyword: " + keyword + " local register failed");
		    		   }
		    	   }
		    	   else{
		    		   printDebug(" $ keyword: " + keyword + " is NOT in local hash range");
		    		   ConnectionDetails connDetails;
		    		   MSDInfo child = Helpers.isInChildHashRange(msdServer.childInfoList, keywordHash);
		    		   if(child != null){	//the keyword is in the specified info child range
		    			   connDetails = child.connectionDetails;
		    			   printDebug(" $ keyword: " + keyword + " is in child's hash range: " + connDetails.hostNameOrIP + ":" + connDetails.port);
		    		   }
		    		   else{
		    			   connDetails = msdServer.parent.info.connectionDetails;
		    			   printDebug(" $ keyword: " + keyword + " is in parent's hash range: " + connDetails.hostNameOrIP + ":" + connDetails.port);
		    		   }
		    		   
		    		   String registerResponse = "";
		    		   String remoteRegisterMessage = "REMOTE-REGISTER 0x0B 10 " + message.arguments.get("[char-set]") + " " +
		    		   									message.arguments.get("[URS Identifier]") + " " +
		    		   									keyword + " " +
		    		   									msdServer.ursInfo.URIString + " " +
		    		   									message.arguments.get("[expiration time]") + " " +		    		   									
		    		   									message.arguments.get("[Lat]") + " " +
		    		   									message.arguments.get("[Long]") + " " +
		    		   									message.arguments.get("[network type]") + " " +
		    		   									message.arguments.get("[stream type]") + " " +
		    		   									"TRUE";
		    		   try {
	    				   //registerResponse = Helpers.sendMessageRcvReply(connDetails, remoteRegisterMessage);
		    			   printDebug(" $ send REMOTE-REGISTER message: \n     " + remoteRegisterMessage);
		    			   CommunicationChannel remoteRegCC = new CommunicationChannel(connDetails);
		    			   remoteRegCC.openSocketOutIn();
		    			   registerResponse = remoteRegCC.sendMessageRcvReply(remoteRegisterMessage);
		    			   printDebug(" $ received status message: \n     " + registerResponse);
		    			   remoteRegCC.closeSocketOutIn();
		    		   } catch (Exception e) {
							e.printStackTrace();
						}
		    		   String[] registerResponseParsed = Helpers.extractFields(registerResponse, "[ ]+");
		    		   if(registerResponseParsed[3].equalsIgnoreCase("FALSE")){
		    			   falseStatusCount++;
		    			   printDebug(" $ REMOTE-REGISTER failed");
		    		   }
		    	   }
		    	   
		       }
		       String result = "TRUE";
		       if(falseStatusCount>0)
		    	   result = "FALSE";
		       response = "REGISTER-STATUS 0x08 1 " + result;
		       printDebug( " $ Response sent to client: \n     " + response);
		       break;
		     case INVALIDATE:
		       printDebug(" $ Starting INVALIDATE logic");	 
		       String keyword = message.arguments.get("[keyword]");
		       msdServer.cache.removeKeywordFromCache(keyword);
		       printDebug(" $ removing keyword: " + keyword + " from local cache");
		       String keywordHash = Helpers.getMD5Hash(keyword);
		       ConnectionDetails connDetails;
		       String messageResponse = "";
		       if(msdServer.info.hashRange.isInRange(keywordHash)){
		    	   //send client redirect to yourself
		    	   connDetails = msdServer.info.connectionDetails;
		    	   messageResponse = "REDIRECT 0x07 6 " + message.arguments.get("[char-set]") + " TRUE " + keyword + " " +
		    	   					connDetails.hostNameOrIP + " " + connDetails.port + " 0";
		    	   printDebug(" $ Keyword is in local hash range send client redirect to self: \n     " + messageResponse);
		       }
		       else{
	    		   
	    		   MSDInfo child = Helpers.isInChildHashRange(msdServer.childInfoList, keywordHash);
	    		   if(child != null){	//the keyword is in the specified info child range
	    			   connDetails = child.connectionDetails;
	    			   printDebug(" $ Keyword is in child's hash range: " + connDetails.hostNameOrIP + ":" + connDetails.port);
	    		   }
	    		   else{
	    			   connDetails = msdServer.parent.info.connectionDetails;
	    			   printDebug(" $ Keyword is in parent's hash range: " + connDetails.hostNameOrIP + ":" + connDetails.port);
	    		   }
	    		   
	    		   
	    		   messageResponse = "MSD-PROBE 0x0B 6 " + message.arguments.get("[char-set]") + " " +
		   									keyword + " " + msdServer.info.connectionDetails.hostNameOrIP + " " +
		   									msdServer.info.connectionDetails.port + " 1 TRUE";
		       }
		       try {
		    	   printDebug(" $ Sending MSD-PROBE to: " + connDetails.hostNameOrIP);
    			   CommunicationChannel invalidateCC = new CommunicationChannel(connDetails);
    			   invalidateCC.openSocketOutIn();
    			   invalidateCC.sendMessage(messageResponse);
				   invalidateCC.closeSocketOutIn();
				} catch (Exception e) {
					e.printStackTrace();
				}
				printDebug(" $ Checking cache to see if remote MSD has MSD-PROBE-REPLY'd to update local cache");
				int sleepCount = 0;
				while(!msdServer.cache.keywordIsInCache(keyword)){
		    		try { Thread.sleep(250);} catch (InterruptedException e) {e.printStackTrace();}
					sleepCount++;
					if(sleepCount >= 80){
						System.out.println("Timed out waiting for the remote MSD to update the cache");
						break;
					}
				}
				if(msdServer.cache.keywordIsInCache(keyword)){
					response = "REDIRECT 0x07 6 " + message.arguments.get("[char-set]") + " TRUE " +
		       		  keyword + " " + 
		       		  msdServer.cache.getConnectionFromCache(keyword).hostNameOrIP + " " + 
		       		  msdServer.cache.getConnectionFromCache(keyword).port + " " +
		       		  msdServer.cache.getConnectionFromCache(keyword).hopCount;
					printDebug(" $ Keyword is now in cache sending REDIRECT to client: \n     " + response);
				}
		       break;
		     case SEARCH:
		       printDebug(" $ Starting SEARCH logic");	
		       ConnectionDetails searchConnDetails = null;
		       String searchMessageResponse = null;
		       String parameter = message.arguments.get("[parameter]");
		       printDebug(" $ SEARCH [parameter]: " + parameter);	
		       int index = parameter.lastIndexOf("%");
		       if(index != -1)
		    	   parameter = parameter.substring(0, index);
		       String[] searchKeywords = Helpers.extractFields(parameter, "[:*&]");
		       printDebug(" $ SEARCH [parameter] parsed keywords: " + Arrays.deepToString(searchKeywords));	
		       for (String searchKeyword : searchKeywords){
		    	   String searchKeywordHash = Helpers.getMD5Hash(searchKeyword);
		    	   if(msdServer.info.hashRange.isInRange(searchKeywordHash)){
		    		   printDebug(" $ keyword: " + searchKeyword + " is in local hash range");	
		    		   List<String> dbRecords = msdServer.database.getRecord(searchKeyword);
		    		   StringBuilder sb = new StringBuilder();
		    		   if(dbRecords != null)
			    		   for(String record: dbRecords){
			    			   if(record == null)
			    				   continue;
			    			   String[] recordSplit = record.split(",");
			    			   sb.append("SEARCH-RESPONSE 0x07 11 " + recordSplit[2] + " global " +
			    					   recordSplit[1] + " " + msdServer.ursInfo.URIString + " " + recordSplit[5] + " " +
			    					   recordSplit[3] + " " + recordSplit[12] + " " + recordSplit[13] + " " +
			    					   recordSplit[14] + " " + recordSplit[16] + " 1\n");
			    		   }
		    		   sb.append("TX-END 0x07 3 " + message.arguments.get("[char-set]") + " " + searchKeyword + " dext");
		    		   out.println(sb.toString());
		    		   searchConnDetails = null;
		    		   printDebug(" $ SEARCH-RESPONSES send to client: \n     " + sb.toString());
		    	   }
		    	   else if(msdServer.cache.keywordIsInCache(searchKeyword)){
		    		   String searchResponse = "REDIRECT 0x07 6 " + message.arguments.get("[char-set]") + " TRUE " + searchKeyword + " " +
	   											msdServer.cache.getConnectionFromCache(searchKeyword).hostNameOrIP + " " + 
	   											msdServer.cache.getConnectionFromCache(searchKeyword).port + " " + 
	   											msdServer.cache.getConnectionFromCache(searchKeyword).hopCount;
		    		   out.println(searchResponse);
		    		   printDebug(" $ keyword: " + searchKeyword + " is in cache sent REDIRECT to client \n     " + searchResponse);	
		    	   }
		    	   else{
		    		   MSDInfo child = Helpers.isInChildHashRange(msdServer.childInfoList, searchKeywordHash);
		    		   if(child != null){	//the keyword is in the specified info child range
		    			   searchConnDetails = child.connectionDetails;
		    			   printDebug(" $ keyword: " + searchKeyword + " is in child's hash range: " + searchConnDetails.hostNameOrIP + ":" + searchConnDetails.port);
		    		   }
		    		   else{
		    			   searchConnDetails = msdServer.parent.info.connectionDetails;
		    			   printDebug(" $ keyword: " + searchKeyword + " is in parent's hash range: " + searchConnDetails.hostNameOrIP + ":" + searchConnDetails.port);
		    		   }
		    		   
		    		   searchMessageResponse = "MSD-PROBE 0x0B 6 " + message.arguments.get("[char-set]") + " " +
							searchKeyword + " " + msdServer.info.connectionDetails.hostNameOrIP + " " +
							msdServer.info.connectionDetails.port + " 1 TRUE";
		    	   }
		    	   if(searchConnDetails != null){
		    		   try {
		    			   CommunicationChannel searchCC = new CommunicationChannel(searchConnDetails);
		    			   searchCC.openSocketOutIn();
		    			   searchCC.sendMessage(searchMessageResponse);
		    			   searchCC.closeSocketOutIn();
		    			   printDebug(" $ sent MSD-PROBE to remote MSD: \n     " + searchMessageResponse);
						} catch (Exception e) {
							e.printStackTrace();
						}
						printDebug(" $ Checking cache to see if remote MSD has MSD-PROBE-REPLY'd to update local cache");
						int searchSleepCount = 0;
						while(!msdServer.cache.keywordIsInCache(searchKeyword)){
				    		try { Thread.sleep(250);} catch (InterruptedException e) {e.printStackTrace();}
				    		searchSleepCount++;
							if(searchSleepCount >= 80){
								System.out.println("Timed out waiting for the remote MSD to update the cache");
								break;
							}
						}
						if(msdServer.cache.keywordIsInCache(searchKeyword)){
							response = "REDIRECT 0x07 6 " + message.arguments.get("[char-set]") + " TRUE " +
							searchKeyword + " " + 
				       		  msdServer.cache.getConnectionFromCache(searchKeyword).hostNameOrIP + " " + 
				       		  msdServer.cache.getConnectionFromCache(searchKeyword).port + " " +
				       		  msdServer.cache.getConnectionFromCache(searchKeyword).hopCount;
							out.println(response);
							printDebug(" $ Keyword is now in cache sending REDIRECT to client: \n     " + response);
						}
		    	   }
		       }
		       sendResponse = false;
		       break;
		     case EXT_SEARCH:
		    	 printDebug(" $ Starting EXT-SEARCH logic");	
			       String extSearchMessageResponse = null;
			       String extSearchParameter = message.arguments.get("[parameter]");
			       printDebug(" $ SEARCH [parameter]: " + extSearchParameter);	
			       int extSearchIndex = extSearchParameter.lastIndexOf("%");
			       if(extSearchIndex != -1)
			    	   extSearchParameter = extSearchParameter.substring(0, extSearchIndex);
			       String[] extSearchKeywords = Helpers.extractFields(extSearchParameter, "[:*&]");
			       printDebug(" $ SEARCH [parameter] parsed keywords: " + Arrays.deepToString(extSearchKeywords));	
			       for (String searchKeyword : extSearchKeywords){
			    	   String searchKeywordHash = Helpers.getMD5Hash(searchKeyword);
			    	   if(msdServer.info.hashRange.isInRange(searchKeywordHash)){
			    		   printDebug(" $ keyword: " + searchKeyword + " is in local hash range");	
			    		   List<String> dbRecords = msdServer.database.getRecord(searchKeyword);
			    		   StringBuilder sb = new StringBuilder();
			    		   if(dbRecords != null)
				    		   for(String record: dbRecords){
				    			   if(record == null)
				    				   continue;
				    			   String[] recordSplit = record.split(",");
				    			   sb.append("EXT-SEARCH-RESPONSE 0x08 11 " + recordSplit[2] + " global " +
				    					   recordSplit[1] + " " + msdServer.ursInfo.URIString + " " + recordSplit[5] + " " +
				    					   recordSplit[3] + " " + recordSplit[12] + " " + recordSplit[13] + " " +
				    					   recordSplit[14] + " " + recordSplit[16] + " 1\n");
				    		   }
			    		   sb.append("TX-END 0x08 3 " + message.arguments.get("[char-set]") + " " + searchKeyword + " dext");
			    		   out.println(sb.toString());
			    		   printDebug(" $ SEARCH-RESPONSES send to client: \n     " + sb.toString());
			    	   }
			    	   else{
			    		   extSearchMessageResponse = "EXT-SEARCH-INVALID 0x08 2 " + 
			    		   								message.arguments.get("[char-set]") + " " + searchKeyword;
			    		   out.println(extSearchMessageResponse);
			    		   printDebug(" $ not in local hash range send EXT-SEARCH-INVALID to client: \n     " + extSearchMessageResponse);
			    	   }
			       }
			       sendResponse = false;
			       break;
		     case GET_BACKUP_MSD:
		    	 printDebug(" $ Starting GET-BACKUP-MSD logic");
		    	 String backupKeyword = message.arguments.get("[keyword]");
		         String backupKeywordHash = Helpers.getMD5Hash(backupKeyword);
		         ConnectionDetails backupConnDetails = null;
		         String backupMessageResponse = "";
		         if(msdServer.cache.keywordIsInCache(backupKeyword)){
		        	 backupMessageResponse = "REDIRECT 0x07 6 " + message.arguments.get("[char-set]") + " TRUE " +
						backupKeyword + " " + 
			       		msdServer.cache.getConnectionFromCache(backupKeyword).hostNameOrIP + " " + 
			       		msdServer.cache.getConnectionFromCache(backupKeyword).port + " " +
			       		msdServer.cache.getConnectionFromCache(backupKeyword).hopCount;
						out.println(backupMessageResponse);
						printDebug(" $ keyword: " + backupKeyword + " is in local cache send REDIRECT to client: \n     " + backupMessageResponse);
		         }
		         else if(msdServer.info.hashRange.isInRange(backupKeywordHash)){
		        	 //send client redirect to yourself
		        	 backupConnDetails = msdServer.info.connectionDetails;
		        	 backupMessageResponse = "REDIRECT 0x07 6 " + message.arguments.get("[char-set]") + " TRUE " + backupKeyword + " " +
		        	 backupConnDetails.hostNameOrIP + " " + backupConnDetails.port + " 0";
		        	 printDebug(" $ keyword: " + backupKeyword + " is in local hash range send REDIRECT to client: \n     " + backupMessageResponse);
		         }
		         else{
		    		   MSDInfo child = Helpers.isInChildHashRange(msdServer.childInfoList, backupKeywordHash);
		    		   if(child != null){	//the keyword is in the specified info child range
		    			   backupConnDetails = child.connectionDetails;
		    			   printDebug(" $ keyword: " + backupKeyword + " is in child's hash range: " + backupConnDetails.hostNameOrIP + ":" + backupConnDetails.port);
		    		   }
		    		   else{
		    			   backupConnDetails = msdServer.parent.info.connectionDetails;
		    			   printDebug(" $ keyword: " + backupKeyword + " is in parent's hash range: " + backupConnDetails.hostNameOrIP + ":" + backupConnDetails.port);
		    		   }
		    		   
		    		   backupMessageResponse = "MSD-PROBE 0x0B 6 " + message.arguments.get("[char-set]") + " " +
							backupKeyword + " " + msdServer.info.connectionDetails.hostNameOrIP + " " +
							msdServer.info.connectionDetails.port + " 1 TRUE";
		    	 }
	    	     if(backupConnDetails != null){
	    		   try {
	    			   CommunicationChannel backupCC = new CommunicationChannel(backupConnDetails);
	    			   backupCC.openSocketOutIn();
	    			   backupCC.sendMessage(backupMessageResponse);
	    			   backupCC.closeSocketOutIn();
	    			   printDebug(" $ sent MSD-PROBE to remote MSD: \n     " + backupMessageResponse);
					} catch (Exception e) {
						e.printStackTrace();
					}
					printDebug(" $ Checking cache to see if remote MSD has MSD-PROBE-REPLY'd to update local cache");
					int backupSleepCount = 0;
					while(!msdServer.cache.keywordIsInCache(backupKeyword)){
			    		try { Thread.sleep(250);} catch (InterruptedException e) {e.printStackTrace();}
			    		backupSleepCount++;
						if(backupSleepCount >= 80){
							System.out.println("Timed out waiting for the remote MSD to update the cache");
							break;
						}
					}
					if(msdServer.cache.keywordIsInCache(backupKeyword)){
						response = "REDIRECT 0x07 6 " + message.arguments.get("[char-set]") + " TRUE " +
						backupKeyword + " " + 
			       		  msdServer.cache.getConnectionFromCache(backupKeyword).hostNameOrIP + " " + 
			       		  msdServer.cache.getConnectionFromCache(backupKeyword).port + " " +
			       		  msdServer.cache.getConnectionFromCache(backupKeyword).hopCount;
						out.println(response);
						printDebug(" $ Keyword is now in cache sending REDIRECT to client: \n     " + response);
					}
	    	    }
		    	sendResponse = false;
		        break;
		     case REMOTE_REGISTER:
		    	 printDebug(" $ Starting REMOTE-REGISTER logic");
		    	 boolean statusRR = true;
		    	 String remoteRegisterKeyword = message.arguments.get("[keyword]");
		    	 String remoteRegisterKeywordHash = Helpers.getMD5Hash(remoteRegisterKeyword);
		    	 	if(msdServer.info.hashRange.isInRange(remoteRegisterKeywordHash)){
		    	 		printDebug(" $ keyword: " + remoteRegisterKeyword + " is in local hashrange");
		    	 		statusRR = msdServer.database.createRecord(remoteRegisterKeywordHash + "," + remoteRegisterKeyword + "," + 
			    				   									message.arguments.get("[char-set]") + "," +
											    				    message.arguments.get("[expiration time]") + "," +
																	"null," +
																	message.arguments.get("[URS Identifier]") + "," +
																	"null," +
																	"null," +
																	"null," +
																	"null," +
																	"null," +
																	"null," +
																	message.arguments.get("[Lat]") + "," +
																	message.arguments.get("[Long]") + "," +
																	message.arguments.get("[network type]") + "," +
																	"null," +
																	message.arguments.get("[stream type]") + " " +
																	"null," +
																	"null," +
																	"null");
			    		   
			    	   }
			    	   else{
			    		   ConnectionDetails remoteRegisterConnDetails;
			    		   MSDInfo child = Helpers.isInChildHashRange(msdServer.childInfoList, remoteRegisterKeywordHash);
			    		   if(child != null){	//the keyword is in the specified info child range
			    			   remoteRegisterConnDetails = child.connectionDetails;
			    			   printDebug(" $ keyword: " + remoteRegisterKeyword + " is in child's hash range: " + remoteRegisterConnDetails.hostNameOrIP + ":" + remoteRegisterConnDetails.port);
			    		   }
			    		   else{
			    			   remoteRegisterConnDetails = msdServer.parent.info.connectionDetails;
			    			   printDebug(" $ keyword: " + remoteRegisterKeyword + " is in parent's hash range: " + remoteRegisterConnDetails.hostNameOrIP + ":" + remoteRegisterConnDetails.port);
			    		   }
			    		   
			    		   String remoteRegisterResponse = "";
			    		   String remoteRegisterMessage = "REMOTE-REGISTER 0x0B 10 " + message.arguments.get("[char-set]") + " " +
			    		   									message.arguments.get("[URS Identifier]") + " " +
			    		   									remoteRegisterKeyword + " " +
			    		   									message.arguments.get("[host URI") + " " +
			    		   									message.arguments.get("[expiration time]") + " " +		    		   									
			    		   									message.arguments.get("[Lat]") + " " +
			    		   									message.arguments.get("[Long]") + " " +
			    		   									message.arguments.get("[network type]") + " " +
			    		   									message.arguments.get("[stream type]") + " " +
			    		   									"TRUE";
			    		   try {
		    				   //registerResponse = Helpers.sendMessageRcvReply(connDetails, remoteRegisterMessage);
			    			   CommunicationChannel remoteRegCC = new CommunicationChannel(remoteRegisterConnDetails);
			    			   remoteRegCC.openSocketOutIn();
			    			   remoteRegisterResponse = remoteRegCC.sendMessageRcvReply(remoteRegisterMessage);
			    			   remoteRegCC.closeSocketOutIn();
			    			   printDebug(" $ sent REMOTE-REGISTER to remote MSD: \n     " + remoteRegisterMessage);
			    		   } catch (Exception e) {
								e.printStackTrace();
							}
			    		   String[] registerResponseParsed = Helpers.extractFields(remoteRegisterResponse, "[ ]+");
			    		   if(registerResponseParsed[3].equalsIgnoreCase("FALSE"))
			    			   statusRR = false;
			    	   }
			    	   
			       
			       String resultRR = "TRUE";
			       if(!statusRR)
			    	   result = "FALSE";
			       response = "REGISTER-STATUS 0x08 1 " + resultRR;
			       printDebug(" $ send REGISTER-STATUS back to originator: \n     " + response);
			       break;
		     case MSD_PROBE:
		       printDebug(" $ Started MSD-PROBE processing.");
//		       response = "if keyword is in has space connect with originator and "+
//		       				"send msd-probe-reply with your info, if not in hash space"+
//		       				"increment the hop count and send updated msd-probe in the"+
//		       				"right direction";
			     //[msd-probe] [message-direction] [6] [char-set] [keyword] [msd IP]
			     //[msd port] [hop count] [inversion flag][\n]
	
			     //Pull out keyword and hash
			        //If(isInMyLocalHash)
			           //Open connection with originator of probe message
			           //Send msd-probe-reply with my connection info
			        //If(isInParentOrChild)
			           //Increment hop count
			           //Forward msd-probe to parent or child with new hop count
			        //Close connections
		       String keywordMSDProbe = message.arguments.get("[keyword]");
		       String keywordHashMSDProbe = Helpers.getMD5Hash(keywordMSDProbe);
		       String msdProbeOriginatorIP = message.arguments.get("[msd IP]");
		       
		       int msdProbeOriginatorPort = 0;
		       try{
		    	   msdProbeOriginatorPort = Integer.parseInt(message.arguments.get("[msd port]"));
		       } catch (NumberFormatException e){
		    	   printError("MSD-Probe port not a valid number.");
		    	   msdProbeOriginatorPort = -1;
		       }
		       
		       int msdProbeHopCount = 0;
		       try{
		    	   msdProbeHopCount = Integer.parseInt(message.arguments.get("[hop count]"));
		       } catch (NumberFormatException e){
		    	   printError("MSD-Probe hop count not a valid number.");
		    	   msdProbeHopCount = -1;
		       }
		       
		       if(msdServer.cache.keywordIsInCache(keywordMSDProbe))
		       {
		    	   //[msd-probe-reply] [0x0B] [6] [char-set] [keyword] [msd IP] [msd port]
		    	   //[hop count] [inversion flag]
		    	   printDebug(" $ MSD-PROBE Cache hit for keyword " + keywordMSDProbe);
		    	   ConnectionDetails cachedConnectionDetails = 
		    		   msdServer.cache.getConnectionFromCache(keywordMSDProbe);
		    	   String msdProbeReplyMessage = "msd-probe-reply 0x0B 6 " +
		    	   		message.arguments.get("[char-set]") + " " +
		    	   		keywordMSDProbe + " " +
		    	   		cachedConnectionDetails.hostNameOrIP + " " + //pull from cache
		    	   		cachedConnectionDetails.port + " " + //pull from cache
		    	   		(msdProbeHopCount+1) + " " +
		    	   		message.arguments.get("[inversion flag]");
		    	   
		    	   printDebug(" $ sending result directly back to " + msdProbeOriginatorIP + ":" + msdProbeOriginatorPort);
		    	   printDebug(" $   result = " + msdProbeReplyMessage);
		    	   ConnectionDetails msdProbeOriginator = new ConnectionDetails(
		    			   msdProbeOriginatorIP, msdProbeOriginatorPort);
		    	   CommunicationChannel msdProbeReplyCC = new CommunicationChannel(msdProbeOriginator);
		    	   msdProbeReplyCC.openSocketOutIn();
    			   msdProbeReplyCC.sendMessage(msdProbeReplyMessage);
    			   msdProbeReplyCC.closeSocketOutIn();
    			   printDebug(" $ sent successfully");
		       }
		       else if(msdServer.info.hashRange.isInRange(keywordHashMSDProbe))
		       {
		    	   //[msd-probe-reply] [0x0B] [6] [char-set] [keyword] [msd IP] [msd port]
		    	   //[hop count] [inversion flag]
		    	   printDebug(" $ MSD-PROBE keyword in my hash range: " + keywordMSDProbe);
		    	   
		    	   String msdProbeReplyMessage = "msd-probe-reply 0x0B 6 " +
		    	   		message.arguments.get("[char-set]") + " " +
		    	   		keywordMSDProbe + " " +
		    	   		msdServer.info.connectionDetails.hostNameOrIP + " " +
		    	   		msdServer.info.connectionDetails.port + " " +
		    	   		(msdProbeHopCount+1) + " " +
		    	   		message.arguments.get("[inversion flag]");
		    	   
		    	   printDebug(" $ sending result directly back to " + msdProbeOriginatorIP + ":" + msdProbeOriginatorPort);
		    	   printDebug(" $   result = " + msdProbeReplyMessage);
		    	   
		    	   ConnectionDetails msdProbeOriginator = new ConnectionDetails(
		    			   msdProbeOriginatorIP, msdProbeOriginatorPort);
		    	   CommunicationChannel msdProbeReplyCC = new CommunicationChannel(msdProbeOriginator);
		    	   msdProbeReplyCC.openSocketOutIn();
    			   msdProbeReplyCC.sendMessage(msdProbeReplyMessage);
    			   msdProbeReplyCC.closeSocketOutIn();
    			   printDebug(" $ sent successfully");
		       }
		       else{
		    	   //[msd-probe] [message-direction] [6] [char-set] [keyword] [msd IP]
				   //[msd port] [hop count] [inversion flag][\n]
		    	   
		    	   printDebug(" $ MSD-PROBE keyword NOT in hash range: " + keywordMSDProbe);
		    	   
		    	   String forwardMSDProbeMessage = "msd-probe 0x0B 6 " +
		    	   		message.arguments.get("[char-set]") + " " +
		    	   		keywordMSDProbe + " " +
		    	   		msdProbeOriginatorIP + " " +
		    	   		msdProbeOriginatorPort + " " +
		    	   		(msdProbeHopCount+1) + " " +
		    	   		message.arguments.get("[inversion flag]");
		    	   
		    	   
	    		   ConnectionDetails connDetailsMSDProbe;
	    		   MSDInfo child = Helpers.isInChildHashRange(msdServer.childInfoList, keywordHashMSDProbe);
	    		   if(child != null)	//the keyword is in the specified info child range
	    		   {
	    			   connDetailsMSDProbe = child.connectionDetails;
	    			   printDebug(" $ forwarding MSD-PROBE to child: " + 
	    					   connDetailsMSDProbe.hostNameOrIP + ":" + connDetailsMSDProbe.port);
			    	   
	    		   }
	    		   else
	    		   {
	    			   connDetailsMSDProbe = msdServer.parent.info.connectionDetails;
	    			   printDebug(" $ forwarding MSD-PROBE to parent: " + 
	    					   connDetailsMSDProbe.hostNameOrIP + ":" + connDetailsMSDProbe.port);
			    	   
	    		   }
	    		   
    			   CommunicationChannel forwardMSDProbeCC = new CommunicationChannel(connDetailsMSDProbe);
    			   forwardMSDProbeCC.openSocketOutIn();
    			   forwardMSDProbeCC.sendMessage(forwardMSDProbeMessage);
    			   forwardMSDProbeCC.closeSocketOutIn();
    			   printDebug(" $ forwarded MSD-PROBE successfully");
	    	   }
		       sendResponse = false; //no response needed
		       break;
		     case MSD_PROBE_REPLY:
		       printDebug(" $ Started MSD-PROBE-REPLY processing.");
		       response = "when received register the keyword and hash in the local cache and "+
		       				"close the connection(this should cause something waiting on the"+
		       				"cache to change to continue)";
		       //[msd-probe-reply] [0x0B] [6] [char-set] [keyword] [msd IP] [msd port]
	    	   //[hop count] [inversion flag]
		       
		       //Pull out keyword and hash
		   	   //Insert into cache
		       //Close connection
		       
		       String keywordMSDProbeReply = message.arguments.get("[keyword]");
		       String msdProbeReplyIP = message.arguments.get("[msd IP]");
		       
		       int msdProbeReplyPort = 0;
		       try{
		    	   msdProbeReplyPort = Integer.parseInt(message.arguments.get("[msd port]"));
		       } catch (NumberFormatException e){
		    	   printError("MSD-Probe port not a valid number.");
		    	   msdProbeReplyPort = -1;
		       }
		       
		       int msdProbeReplyHopCount = 0;
		       try{
		    	   msdProbeReplyHopCount = Integer.parseInt(message.arguments.get("[hop count]"));
		       } catch (NumberFormatException e){
		    	   printError("MSD-Probe hop count not a valid number.");
		    	   msdProbeReplyHopCount = -1;
		       }
		       
		       printDebug(" $ adding keyword: " + keywordMSDProbeReply);
		       printDebug(" $        connection: " + msdProbeReplyIP + ":" + msdProbeReplyPort);
		       printDebug(" $ to cache.");
		       
		       ConnectionDetails connDetailsMSDProbeReply = new ConnectionDetails(msdProbeReplyIP, msdProbeReplyPort);
		       connDetailsMSDProbeReply.hopCount = msdProbeReplyHopCount + 1;
		       msdServer.cache.addKeywordToCache(keywordMSDProbeReply, connDetailsMSDProbeReply );
		       sendResponse = false;
		       break;
		     case HELLO:
		       printDebug(" $ Started HELLO processing.");
		       //[hello] [0x0B] [5] [domain count] [ID hash] [msd IP] [msd port] [pmcast status]
				//If(not in child list)
		    	 	//Add to child list
				//Parse and update child connection info and domain counts
				//Wait til received first REP-HELLO from parent  while (hash space null) {wait}
				//Send REP-HELLO back to child and allocate appropriate piece of hash space based on domain counts
//		       MSDInfo child = new MSDInfo();
//		       ConnectionDetails childSenderConnectionDetails = new ConnectionDetails();
//		       childSenderConnectionDetails.hostNameOrIP
		    	 MSDInfo child = new MSDInfo();
			       try{
			    	   child.domainCount = Integer.parseInt(message.arguments.get("[domain count]"));}
			       catch(NumberFormatException e){
			    	   printError("Bad count value, ignoring HELLO");
			    		 break; //bad count value, ignore HELLO
			    	 }
			     child.idHash = message.arguments.get("[ID hash]");
		    	 child.connectionDetails.hostNameOrIP = message.arguments.get("[msd IP]");
		    	 try{
			    	   child.connectionDetails.port = Integer.parseInt(message.arguments.get("[msd port]"));}
			       catch(NumberFormatException e){
			    	   printError("Bad port value, ignoring HELLO");
			    		 break; //bad count value, ignore HELLO
			    	 }
		       
			   boolean newCount = true;
		       MSDInfo oldChild = Helpers.findChildByConnectionDetails(msdServer.childInfoList,
		    		   child.connectionDetails);
		       if(oldChild == null) //is not already a child
		       {
		    	   printDebug(" $ This HELLO is coming from a new child.");
		    	   newCount = true;
		    	   msdServer.childInfoList.add(child); //add to child list
		       }
		       else
		       {
		    	   printDebug(" $ This HELLO is coming from an old child.");
		    	   //update old child info
		    	   if(oldChild.domainCount != child.domainCount)
		    		   newCount = true;
		    	   else
		    		   newCount = false;
		    	   oldChild.domainCount = child.domainCount;
		    	   oldChild.idHash = child.idHash;
		    	   oldChild.connectionDetails.hostNameOrIP = child.connectionDetails.hostNameOrIP;
		    	   oldChild.connectionDetails.port = child.connectionDetails.port;
		       }   
		       
	    	   if(!msdServer.isRoot)
	    	   {
	    		   int repHelloTimer = 0;
	    		   int repHelloTimeout = 300000; //20 seconds
	    		   int waitPeriod = 250; // 250 mseconds
		    	   while(!msdServer.info.overallHashRange.hashRangeInitialized())
		    	   {
		    		   if(repHelloTimer == 0)
		    			   printDebug(" $ Waiting to get hash range from parent.");
		    		   //wait here until our hash range is initialized so we know
		    		   //what to give our children
		    		   //sleep 1/4 second and try again
		    		   repHelloTimer += waitPeriod;
		    		   try { Thread.sleep(waitPeriod);} catch (InterruptedException e) {e.printStackTrace();}
		    		   if(repHelloTimer > repHelloTimeout)
		    		   {
		    			   printDebug(" $ Never got hash range from parent. Timed out.");
		    			   break; //this is a timeout, will only wait 20 seconds for parent to respond
		    		   }
		    	   }
		    	   if(repHelloTimer <= repHelloTimeout)
	    		   {
		    		   printDebug(" $ Found assigned hash range, so we can send a REP-HELLO back.");
	    		   }
	    	   }
	    	   //send REP-HELLO back
	    	   //[rep-hello] [message direction] [3] [ID hash] [address start] [address end]
	    	   //rep-hello 0x0B 3 msdServer.info.idHash childAddressStart childAddressEnd
	    	   response = "rep-hello 0x0B 3 ";
	    	   String responseHashStart = null;
	    	   String responseHashEnd = null;
	    	   if(!newCount)
	    	   {
	    		   printDebug(" $ Sending back same old hash range in REP-HELLO");
	    		   //just send back the previously assigned hash range
	    		   responseHashStart = oldChild.hashRange.hashStart;
	    		   responseHashEnd = oldChild.hashRange.hashEnd;
	    	   }
	    	   else //new domain count so calculate all the new hash ranges
	    	   {
	    		   printDebug(" $ Something new arrived (child or domain count), so we must redefine hash space.");
	    		   reDefineHashSpace();
	    		   for (MSDInfo currentChild : msdServer.childInfoList)
	    		   {
		 			  if(child.idHash.equals(currentChild.idHash))
					  {
						  responseHashStart = currentChild.hashRange.hashStart;
						  responseHashEnd = currentChild.hashRange.hashEnd;
					  }
	    		   }
	    	   }
			
		       //send back this childs possibly new hash range
			   response += msdServer.info.idHash + " " + 
			  		responseHashStart + " " + responseHashEnd;
			   printDebug(" $ REP-HELLO response back to child: " + response);
		       break;
		     case REP_HELLO:
		       printDebug(" $ Started REP-HELLO processing.");
		       //[rep-hello] [message direction] [3] [ID hash] [address start]
		       //[address end][\n]
		    	 
		       String parentIDHash = message.arguments.get("[ID hash]");
		       String newAddressStart = message.arguments.get("[address start]");
		       String newAddressEnd = message.arguments.get("[address end]");
		       
		       if(!parentIDHash.equals(msdServer.parent.info.idHash))
		       {
		    	   printDebug(" $ Received new parent ID hash: " + parentIDHash);
		    	   msdServer.parent.info.idHash = parentIDHash;
		       }
		       if(!newAddressStart.equals(msdServer.info.overallHashRange.hashStart)
		    		   || !newAddressEnd.equals(msdServer.info.overallHashRange.hashEnd))
		       {
		    	   printDebug(" $ Received new hash range: " + newAddressStart + "-" + newAddressEnd);
		    	   msdServer.info.overallHashRange.hashStart = newAddressStart;
		    	   msdServer.info.overallHashRange.hashEnd = newAddressEnd;
		    	   printDebug(" $ Redefining hash space among my children and I");
		    	   reDefineHashSpace();
		       }
		       sendResponse = false;
		       break;
		     case ADD_SPACE:
		       printDebug(" $ Started ADD-SPACE processing.");
//		       response = "if hash space is null (uninitialized) parse and update"+
//		       				"hash space, then allocate range between yourself and"+
//		       				"children only keeping 1 of n+1 parts where n is your"+
//		       				"number of children, if hash space if different than"+
//		       				"current update your hash space and send add-space to"+
//		       				"children with new ranges";
		       //[add-space] [message direction] [4] [range start] [range end]
		       //[significant bits] [ID hash][\n]
		       //If(hashSpaceUndefined)
				  //Parse and update hash space
				  //Allocate range appropriately between self and children based on domain counts. You always get 1 part
			   //If(hashSpaceDifferent)
				  //Send ADD-SPACE to children with new ranges
		       
		       String newAddressStartAddSpace = message.arguments.get("[range start]");
		       String newAddressEndAddSpace = message.arguments.get("[range end]");
		       int significantBitsAddSpace = 0;
		       try{
		    	   significantBitsAddSpace = Integer.parseInt(message.arguments.get("[significant bits]"));
		       } catch (NumberFormatException e){
		    	   significantBitsAddSpace = -1;
		       }
		       
		       if(!newAddressStartAddSpace.equals(msdServer.info.overallHashRange.hashStart)
		    		   || !newAddressEndAddSpace.equals(msdServer.info.overallHashRange.hashEnd))
		       {
		    	   printDebug(" $ Received a new or different hash space: +" +
		    			   newAddressStartAddSpace + "-" + newAddressEndAddSpace);
		    	   msdServer.info.overallHashRange.hashStart = newAddressStartAddSpace;
		    	   msdServer.info.overallHashRange.hashEnd = newAddressEndAddSpace;
		    	   msdServer.info.overallHashRange.significantBits = significantBitsAddSpace;
		    	   
		    	   printDebug(" $ Need to redefine hash space among my children and I");
		    	   reDefineHashSpace();
		    	   
		    	   //set a flag here that will trigger a forced immediate send
		    	   //of the ADD-SPACE messages to children
		    	   printDebug(" $ Hash space updated so trigger a forced immediate resend of ADD-SPACE");
		    	   msdServer.forceImmediateAddSpaceSend = true;
		    	   
		    	   //TODO: migrate any old DB records using REMOTE-REGISTER
		       }
		       else
		       {
		    	   printDebug(" $ Same hash space received from parent");
		       }
		       
		       sendResponse = false;
		       break;
		     case UNICAST_UPDATE_STATUS:
		       printDebug(" $ Started UNICAST-UPDATE-STATUS processing.");
		       //[unicast-update-status] [0x09] [1] [true][\n]
		       boolean unicastUpdateStatusStatus;
		       if(message.arguments.get("[status]").toLowerCase().equals("true"))
		       {
		    	   unicastUpdateStatusStatus = true;
		    	   printDebug(" $ UNICAST-UPDATE-STATUS received true status");
		       }
		       else
		       {
		    	   unicastUpdateStatusStatus = false;
		    	   printDebug(" $ UNICAST-UPDATE-STATUS received false status");
		       }
		       
		       response = "bye 0x02 0";
		       printDebug(" $ respond to UNICAST-UPDATE-STATUS with a bye: " + response);
		       
		       break;
		     case RESYNC_STATUS:
		       printDebug(" $ Started RESYNC-STATUS processing.");
		       //type 1: [resync-status] [0x09] [3] [true] [msd IP] [msd port][\n]
		       //type 2: [resync-status] [0x09] [1] [false][\n]
		       if(message.numFields == 1)
		       {
		    	   printDebug(" $ RESYNC-STATUS false received");
		       }
		       else
		       {
		    	   printDebug(" $ RESYNC-STATUS true received with new MSD info: ");
		    	   msdServer.parent.info.connectionDetails.hostNameOrIP = 
		    		   message.arguments.get("[msd IP]");
		    	   msdServer.parent.info.connectionDetails.port = 
		    		   Integer.parseInt(message.arguments.get("[msd port]"));
		    	   printDebug(" $   IP: " + msdServer.parent.info.connectionDetails.hostNameOrIP);
		    	   printDebug(" $   port: " + msdServer.parent.info.connectionDetails.port);
		       }
		       response = "bye 0x02 0";
		       printDebug(" $ respond to RESYNC-STATUS with a bye: " + response);
		       
		       break;
		     //URS -> MSD end
		       
		    }
		}
		
		if(sendResponse)
		{
			print(" $ processMessage() Response: " + response);
			out.println(response); //send back a response
		}
		else
			print(" $ processMessage() no further response necessary.");
			
	}
	
	private void reDefineHashSpace()
	{
		  String addressStartHex32 = msdServer.info.overallHashRange.hashStart;
		  String addressEndHex32 = msdServer.info.overallHashRange.hashEnd;
		  int childCountsTotal = Helpers.getChildCounts(msdServer.childInfoList);
		  
		  
		  String[][] keywordHashBoundaries = Helpers.getHashBoundaries(addressStartHex32, addressEndHex32, childCountsTotal+1);
		  //printToConsole("All boundaries:" + Arrays.deepToString(keywordHashBoundaries));
		  //give this server first boundary
		  msdServer.info.hashRange.hashStart = keywordHashBoundaries[0][0];
		  msdServer.info.hashRange.hashEnd = keywordHashBoundaries[0][1];
		  if(childCountsTotal == 0)
			  return; //no need to redefine if you have no children
		  //printToConsole("Root range: " + myRangeStart + " " + myRangeEnd);
		  int childCount;
		  int pointer = 1;
		  for(int i=0; i < msdServer.childInfoList.size(); i++)
		  {
			  MSDInfo currentChild = msdServer.childInfoList.get(i);
			  childCount = currentChild.domainCount;
			  String childRangeStart = keywordHashBoundaries[pointer][0];
			  pointer += childCount;
			  String childRangeEnd = keywordHashBoundaries[pointer-1][1];
			  currentChild.hashRange.hashStart = childRangeStart;
			  currentChild.hashRange.hashEnd = childRangeEnd;
		  }
		
	}

	public void printError(String errorMessage)
	{
		//can put logic in here to print to log file if needed
		System.err.println(Thread.currentThread().getId() + ": ERROR: " + errorMessage);
	}

	public void print(String message)
	{
		//can put logic in here to print to log file if needed
		System.out.println(Thread.currentThread().getId() + ": " + message);
	}
	
	public void printDebug(String debugMessage)
	{
		//can put logic in here to print to log file if needed
		if(msdServer.debugOutput)
			System.out.println(Thread.currentThread().getId() + ": DEBUG: " + debugMessage);
	}
	  
}